package com.common.dao;

import java.io.Serializable;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

public class GenericDaoImpl<T, PK extends Serializable> extends
		HibernateDaoSupport implements GenericDao<T, PK> {

	protected final Log log = LogFactory.getLog(getClass());

	final Class<T> entityClass;

	/**
	 * Constructor that takes in a class to see which type of entity to persist.
	 * 
	 * @param persistentClass
	 *            the class type you'd like to persist
	 */
	public GenericDaoImpl(final Class<T> persistentClass) {
		this.entityClass = persistentClass;
	}

	@SuppressWarnings("unchecked")
	public List<T> getAll() {
		// return getHibernateTemplate().loadAll(this.entityClass);
		return findByCriteria(DetachedCriteria.forClass(entityClass));
	}

	@SuppressWarnings("unchecked")
	public List<T> getAllDistinct() {
		Collection result = new LinkedHashSet(getAll());

		return new ArrayList(result);
	}

	@SuppressWarnings("unchecked")
	public T get(PK id) {
		T entity = getHibernateTemplate().get(this.entityClass, id);
		if (entity == null) {
			log.warn("Uh oh, '" + this.entityClass + "' object with id '" + id
					+ "' not found...");
			throw new ObjectRetrievalFailureException(this.entityClass, id);
		}

		return entity;
	}

	@SuppressWarnings("unchecked")
	public boolean exists(PK id) {
		T entity = getHibernateTemplate().get(this.entityClass, id);

		return entity != null;
	}

	// Note: Hibernate3's merge operation does not reassociate the object
	// with the current Hibernate Session. Instead, it will always copy the
	// state over to a registered representation of the entity. In case of a
	// new entity, it will register a copy as well, but will not update the
	// id of the passed-in object. To still update the ids of the original
	// objects too, we need to register Spring's
	// IdTransferringMergeEventListener on our SessionFactory.
	@SuppressWarnings("unchecked")
	public T save(T object) {
		return getHibernateTemplate().merge(object);
	}

	public void saveAll(Collection<T> col) {
		getHibernateTemplate().saveOrUpdateAll(col);
	}

	public void remove(PK id) {
		getHibernateTemplate().delete(this.get(id));
	}

	public void removeAll(Collection<T> col) {
		getHibernateTemplate().deleteAll(col);
	}

	@SuppressWarnings("unchecked")
	public List<T> findByPagination(final DetachedCriteria criteria,
			final int start, final int limit) {
		if (criteria == null) {
			return findByCriteria(DetachedCriteria.forClass(entityClass),
					start, limit);
		}

		return findByCriteria(criteria, start, limit);
	}

	@SuppressWarnings("unchecked")
	private List<T> findByCriteria(final DetachedCriteria criteria,
			final int start, final int limit) {
		return (List<T>) getHibernateTemplate().execute(
				new HibernateCallback<T>() {
					public T doInHibernate(Session session)
							throws HibernateException, SQLException {
						Criteria _criteria = criteria
								.getExecutableCriteria(session);
						if (start > 0 && limit > 0) {
							_criteria.setFirstResult(start);
							_criteria.setMaxResults(limit);
						}
						_criteria.setCacheable(true);

						return (T) _criteria.list();
					}
				});
	}

	@SuppressWarnings("unchecked")
	public List<T> findByCriteria(final DetachedCriteria criteria) {
		// HibernateSqlTranslator.getSqlString(getSession(), criteria);
		return findByPagination(criteria, -1, -1);
	}

	public boolean isPropertyUnique(String propertyName, Object value) {
		DetachedCriteria dc = DetachedCriteria.forClass(entityClass);
		dc.add(Restrictions.eq(propertyName, value));

		return findByCriteria(dc).size() == 0;
	}

	public boolean isPropertyUnique(String propertyName, Object newValue,
			Object oldValue) {
		if (newValue == null || newValue.equals(oldValue)) {
			return true;
		}

		return isPropertyUnique(propertyName, newValue);
	}
}
